package com.dps.metrics.factory;

import com.dps.metrics.metadata.MonitorMetricsMetadata;
import com.dps.metrics.type.Counter;
import com.dps.metrics.type.Gauge;
import com.dps.metrics.type.Timer;
import io.micrometer.core.instrument.ImmutableTag;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;

public class MicrometerMetricsFactory implements MetricsFactory {
    private static final Logger log = LoggerFactory.getLogger(MicrometerMetricsFactory.class);
    private static final ConcurrentMap<List<String>, LongAdder> GAUGE_COLLECTOR = new ConcurrentHashMap<>();
    private final MeterRegistry meterRegistry;

    public MicrometerMetricsFactory(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    private static List<String> cacheMetricsKey(String name, Map<String, String> tags) {
        if (tags != null && tags.size() != 0) {
            List<String> valueList = new ArrayList(tags.values());
            valueList.add(name);
            return valueList;
        } else {
            return Collections.singletonList(name);
        }
    }

    public Counter createCounter(final String name, final Map<String, String> tags, final String help) {
        return new Counter() {
            private final io.micrometer.core.instrument.Counter counter;

            {
                this.counter = io.micrometer.core.instrument.Counter.builder(name).tags(MicrometerMetricsFactory.this.translateTags(tags)).description(help).register(MicrometerMetricsFactory.this.meterRegistry);
            }

            public void inc(long delta) {
                this.counter.increment((double) delta);
            }
        };
    }

    public com.dps.metrics.type.Timer createTimer(final String name, final Map<String, String> tags, final String help) {
        return new Timer() {
            private final io.micrometer.core.instrument.Timer timer;

            {
                this.timer = io.micrometer.core.instrument.Timer.builder(name).publishPercentiles(0.99).tags(MicrometerMetricsFactory.this.translateTags(tags)).description(help).register(MicrometerMetricsFactory.this.meterRegistry);
            }

            public void durationNanoSeconds(long nanoSeconds) {
                this.timer.record(nanoSeconds, TimeUnit.NANOSECONDS);
            }
        };
    }

    public Gauge createGauge(final String name, final Map<String, String> tags, final String help) {
        return new Gauge() {
            private LongAdder getOrInitGauge(String namex, Map<String, String> tagsx) {
                List<String> key = MicrometerMetricsFactory.cacheMetricsKey(namex, tagsx);
                LongAdder g = (LongAdder) MicrometerMetricsFactory.GAUGE_COLLECTOR.get(key);
                if (g != null) {
                    return g;
                } else {
                    LongAdder newStateObject = new LongAdder();
                    io.micrometer.core.instrument.Gauge.builder(namex, newStateObject, LongAdder::longValue).description(help).tags(MicrometerMetricsFactory.this.translateTags(tagsx)).register(MicrometerMetricsFactory.this.meterRegistry);
                    LongAdder stateObject = (LongAdder) MicrometerMetricsFactory.GAUGE_COLLECTOR.putIfAbsent(key, newStateObject);
                    if (stateObject == null) {
                        GlobalMetricsFactory.getGlobal().createCounter(MonitorMetricsMetadata.GAUGE_COLLECTOR_SIZE, (Map) null).inc(1L);
                        return newStateObject;
                    } else {
                        return stateObject;
                    }
                }
            }

            public void incConc() {
                this.getOrInitGauge(name, tags).increment();
            }

            public void decConc() {
                this.getOrInitGauge(name, tags).decrement();
            }

            public void add(long mount) {
                this.getOrInitGauge(name, tags).add(mount);
            }

            public void subtract(long mount) {
                this.getOrInitGauge(name, tags).add(-mount);
            }

            public void set(long newValue) {
                LongAdder gaugeState = this.getOrInitGauge(name, tags);
                gaugeState.reset();
                gaugeState.add(newValue);
            }
        };
    }

    private Iterable<Tag> translateTags(Map<String, String> tags) {
        if (tags == null) {
            tags = new HashMap(1);
        }

        List<Tag> tagList = new ArrayList(((Map) tags).size());
        Iterator iterator = ((Map) tags).entrySet().iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, String> tag = (Map.Entry) iterator.next();
            tagList.add(new ImmutableTag((String) tag.getKey(), tag.getValue() == null ? "" : (String) tag.getValue()));
        }

        return tagList;
    }
}
